home *** CD-ROM | disk | FTP | other *** search
/ PCGUIA 117 / PC Guia 117.iso / Software / Utils / Software2 / Product11 / Setup.exe / MT-3.16-full-en_US / lib / MT.pm
Text File  |  2005-04-18  |  75KB  |  2,104 lines

  1. # Copyright 2001-2005 Six Apart. This code cannot be redistributed without
  2. # permission from www.movabletype.org.
  3. #
  4. # $Id: MT.pm.pre 11285 2005-04-14 00:15:40Z ezra $
  5.  
  6. package MT;
  7. use strict;
  8.  
  9. use vars qw( $VERSION );
  10. $VERSION = '3.16';
  11.  
  12. use MT::ConfigMgr;
  13. use MT::Object;
  14. use MT::Blog;
  15. use MT::Util qw( start_end_day start_end_week start_end_month start_end_period
  16.                  archive_file_for get_entry );
  17. use File::Spec;
  18. use File::Basename;
  19. use Fcntl qw( LOCK_EX );
  20.  
  21. use MT::ErrorHandler;
  22. @MT::ISA = qw( MT::ErrorHandler );
  23.  
  24. use vars qw( %Text_filters );
  25.  
  26. sub version_number {
  27.     (my $ver = $VERSION) =~ s/[^\d\.].*$//;
  28.     $ver;
  29. }
  30.  
  31. sub version_slug {
  32.     return <<SLUG;
  33. Powered by Movable Type
  34. Version $VERSION
  35. http://www.movabletype.org/
  36. SLUG
  37. }
  38.  
  39. my $mt;
  40.  
  41. sub instance {
  42.     return $mt if ($mt);
  43.  
  44.     my $class = shift;
  45.     $mt = bless { }, $class;
  46.     $mt->init(@_) or
  47.         return $class->error($mt->errstr);
  48.     $mt;
  49. }
  50.  
  51. sub unplug {
  52.     $mt = undef;
  53. }
  54.  
  55. sub log {
  56.     shift if ref $_[0];
  57.     require MT::Log;
  58.     my $logentry = new MT::Log();
  59.     $logentry->message($_[0]);
  60.     $logentry->save();
  61.     print STDERR "Message: " . $_[0] . "\n";
  62. }
  63. my $plugin_envelope;           # this is used by the plugin loader to tell
  64.                                # add_plugin what directory the plugin is in.
  65. use vars '@Plugins';
  66.  
  67. sub add_plugin {
  68.     my $class = shift;
  69.     my ($plugin) = @_;
  70.     $plugin->envelope($plugin_envelope);
  71.     push @Plugins, $plugin if UNIVERSAL::isa($plugin, 'MT::Plugin');
  72. }
  73.  
  74. use vars '%PluginActions';
  75. sub add_plugin_action {
  76.     my $class = shift;
  77.     my ($object_type, $action_link, $link_text) = @_;
  78.     $action_link .= '?' unless $action_link =~ m.\?.;
  79.     push @{$PluginActions{$object_type}},
  80.          { page => MT::ConfigMgr->instance()->CGIPath . 
  81.            '/' . $plugin_envelope . 
  82.            '/' . $action_link,
  83.        link_text => $link_text };
  84. }
  85.  
  86. my %Methods = (
  87.     PreEntrySave => 'MT::Entry::pre_save',
  88.     PreCommentSave => 'MT::Comment::pre_save',
  89.     CommentThrottleFilter => 'CommentThrottleFilter',
  90.     TBPingThrottleFilter => 'TBPingThrottleFilter',
  91.     AppPostEntrySave => 'AppPostEntrySave',
  92.     CommentFilter => 'CommentFilter',
  93.     TBPingFilter => 'TBPingFilter',
  94.     BuildFileFilter => 'BuildFileFilter',
  95.     BuildFile => 'BuildFile',
  96.     PeriodicTask => 'PeriodicTask',
  97. );
  98. my @Callbacks;
  99. sub add_callback {
  100.     my $class = shift;
  101.     my($meth, $priority, $plugin, $code) = @_;
  102.     if (ref $plugin && !UNIVERSAL::isa($plugin, "MT::Plugin")) {
  103.     return $class->error("If present, 3rd argument to add_callback "
  104.                  . "must be an object of type MT::Plugin");
  105.     }
  106.     if ((ref$code) ne 'CODE') {
  107.     return $class->error('4th argument to add_callback must be '
  108.                  . 'a CODE reference.');
  109.     }
  110.     unless ($meth =~ /::/) {
  111.         $meth = $Methods{$meth}
  112.             or return $class->error("Invalid callback name $meth");
  113.     }
  114.     # 0 and 11 are exclusive.
  115.     if ($priority == 0 || $priority == 11) {
  116.     if ($Callbacks[$priority]->{$meth}) {
  117.         return $class->error("Two plugins are in conflict");
  118.     }
  119.     }
  120.     return $class->error("Invalid priority level $priority at add_callback")
  121.     if (($priority < 0) || ($priority > 11));
  122.     require MT::Callback;
  123.     push @{$Callbacks[$priority]->{$meth}}, new MT::Callback(plugin => $plugin, 
  124.                               code => $code,
  125.                               priority => $priority,
  126.                               method => $meth);
  127. }
  128.  
  129. use vars qw( $CB_ERR );
  130.  
  131. sub callback_error { $CB_ERR = $_[0]; }
  132. sub callback_errstr { $CB_ERR }
  133.  
  134. # A callback should return a true/false value. The result of
  135. # run_callbacks is the logical AND of all the callback's return
  136. # values. Some hookpoints will ignore the return value: e.g. object
  137. # callbacks don't use it. By convention, those that use it have Filter
  138. # at the end of their names (CommentPostFilter, CommentThrottleFilter,
  139. # etc.)
  140. # Note: this composition is not short-circuiting. All callbacks are
  141. # executed even if one has already returned false.
  142. # ALSO NOTE: failure (dying or setting $cb->errstr) does not force a
  143. # "false" return.
  144. # THINK: are there cases where a true value should override all false values?
  145. # that is, where logical OR is the right way to compose multiple callbacks?
  146. sub run_callbacks {
  147.     my $class = shift;
  148.     my($meth, @args) = @_;
  149.     my @errors;
  150.     my $filter_value = 1;
  151.     my $result = eval {
  152.     my $try_callback = sub {       # return value of $try_callback is ignored
  153.         my ($cb) = @_;
  154.         $cb->error();                         # reset the error string
  155.         if (ref $cb->{code} eq 'CODE') {
  156.         my $result = eval {
  157.             my $temp = $cb->invoke(@args);
  158.             $filter_value &&= $temp;
  159.         }; if ($@) {
  160.                     push @errors, $@;
  161.             $class->instance->log(($cb->{plugin} ? 
  162.                  ($cb->{plugin}->name() || "Unnamed plugin") :
  163.                  "Unnamed plugin")
  164.                 . " died with: " . $@);
  165.             return 0;
  166.         }
  167.         if ($cb->errstr() =~ /\w/) {
  168.                     push @errors, $cb->errstr();
  169.             $class->instance->log(($cb->{plugin} ? 
  170.                  ($cb->{plugin}->name() || "Unnamed plugin") :
  171.                  "Unnamed plugin")
  172.                 . " returned error: "
  173.                 . $cb->errstr());
  174.             return 0;
  175.         }
  176.         } else { return 0; }
  177.         return 1;
  178.     };
  179.  
  180.     foreach my $callback_sheaf (@Callbacks) {
  181.         for my $cb (@{$callback_sheaf->{$meth}}) {
  182.         $try_callback->($cb) || next;
  183.         }
  184.     }
  185.     1;
  186.     };
  187.     $class->instance->log("Callback died: " . $@) if !$result;
  188.     callback_error(join(" ", @errors));
  189.     return $filter_value;
  190. }
  191.  
  192. sub new {
  193.     &instance;
  194. }
  195.  
  196. sub init {
  197.     my $mt = shift;
  198.     my %param = @_;
  199.  
  200.     MT->add_text_filter(__default__ => {
  201.         label => 'Convert Line Breaks',
  202.         on_format => sub { MT::Util::html_text_transform($_[0]) },
  203.     });
  204.  
  205.     ## Initialize the language to English in case any errors occur in
  206.     ## the rest of the initialization process.
  207.     $mt->set_language('en_US'); 
  208.     my $cfg = $mt->{cfg} = MT::ConfigMgr->instance;
  209.     my($cfg_file);
  210.     unless ($cfg_file = $param{Config}) {
  211.         for my $f (qw( mt.cfg )) {
  212.             $cfg_file = $f, last if -r $f;
  213.         }
  214.     }
  215.     if ($cfg_file) {
  216.         $cfg->read_config($cfg_file) or
  217.             return $mt->error($cfg->errstr);
  218.     }
  219.     $mt->{mt_dir} = "";
  220.     if (my $dir = $param{Directory}) {
  221.         $mt->{mt_dir} = $dir;
  222.     }
  223.     require File::Spec;
  224.     $mt->{mt_dir} = dirname($cfg_file||"") || ""
  225.         if !$mt->{mt_dir} || !File::Spec->file_name_is_absolute($mt->{mt_dir});
  226.     $mt->{mt_dir} = $ENV{PWD} || "" if !$mt->{mt_dir}
  227.                            || !File::Spec->file_name_is_absolute($mt->{mt_dir});
  228.     $mt->{mt_dir} = dirname($0) if !$mt->{mt_dir}
  229.                            || !File::Spec->file_name_is_absolute($mt->{mt_dir});
  230.  
  231.     $mt->{mt_dir} = dirname($ARGV[0] || "")
  232.         if (!$mt->{mt_dir} || (!File::Spec->file_name_is_absolute($mt->{mt_dir}))
  233.             && $ARGV[0]);
  234.     $mt->{mt_dir} = dirname($ENV{SCRIPT_FILENAME}) 
  235.         if (!$mt->{mt_dir} || (!File::Spec->file_name_is_absolute($mt->{mt_dir}))
  236.             && $ENV{SCRIPT_FILENAME});
  237.  
  238.     # determine app directory path
  239.     $mt->{app_dir} = $ENV{PWD} || "";
  240.     $mt->{app_dir} = dirname($0) if !$mt->{app_dir}
  241.                            || !File::Spec->file_name_is_absolute($mt->{app_dir});
  242.     $mt->{app_dir} = dirname($ENV{SCRIPT_FILENAME}) 
  243.         if (!$mt->{app_dir} || (!File::Spec->file_name_is_absolute($mt->{app_dir}))
  244.             && $ENV{SCRIPT_FILENAME});
  245.  
  246.     my %default_dirs = (
  247.         TemplatePath =>  'tmpl',
  248.         CSSPath => 'css',
  249.         ImportPath => 'import',
  250.         PluginPath => 'plugins',
  251.         SearchTemplatePath => 'search_templates',
  252.     );
  253.     my $config_dir = dirname($cfg_file||"");
  254.     for my $meth (keys %default_dirs) {
  255.         $cfg->$meth(File::Spec->catfile($config_dir, $default_dirs{$meth}))
  256.             unless defined $cfg->$meth();
  257.     }
  258.     if ($cfg->ObjectDriver =~ /DBI::(?:mysql|postgres)/) {
  259.         my $pass_file = File::Spec->catfile($config_dir, 'mt-db-pass.cgi');
  260.         local *FH;
  261.         if (open FH, $pass_file) {
  262.             my $pass = <FH>;
  263.             close FH;
  264.             if ($pass) {
  265.                 chomp($pass);
  266.                 $pass =~ s!^\s*!!;
  267.                 $pass =~ s!\s*$!!;
  268.             }
  269.             $cfg->DBPassword($pass);
  270.         }
  271.     }
  272.     MT::Object->set_driver($cfg->ObjectDriver)
  273.         or return $mt->error("Bad ObjectDriver config: "
  274.                  . MT::ObjectDriver->errstr);
  275.     MT::Object->set_callback_routine(\&run_callbacks);
  276.  
  277.     $mt->set_language($cfg->DefaultLanguage);
  278.     # FIXME: these are request-long caches; should be in MT::Request
  279.     $mt->{__rebuilt} = {};
  280.     $mt->{__cached_maps} = {};
  281.     $mt->{__cached_templates} = {};
  282.     my $PluginPath = $cfg->PluginPath;
  283.     local *DH;
  284.     if (opendir DH, $PluginPath) {
  285.         my @p = readdir DH;
  286.         PLUGIN:
  287.         for my $plugin (@p) {
  288.         next if ($plugin =~ /^\./ || $plugin =~ /~$/);
  289.  
  290.         my $load_plugin = sub {
  291.         my ($plugin) = @_;
  292.         die "Bad plugin filename '$plugin'"
  293.             if ($plugin !~ /^([-\\\/\@\:\w\.\s~]+)$/);
  294.         $plugin = $1;
  295.         eval { require $plugin };
  296.                 if ($@) {
  297.                     $mt->log("Plugin error: $plugin $@");
  298.                     return 0;
  299.                 }
  300.                 return 1;
  301.         };
  302.  
  303.         my $plugin_full_path = File::Spec->catfile($PluginPath, $plugin);
  304.         if (-f $plugin_full_path) {
  305.         $plugin_envelope = 'plugins';
  306.         $load_plugin->($plugin_full_path)
  307.             if ($plugin_full_path =~ /\.pl$/ && $plugin_full_path !~ m{(/|^)\.[^/]*$});
  308.         } else {
  309.         $plugin_envelope = "plugins/" . $plugin;
  310.         opendir SUBDIR, $plugin_full_path;
  311.         my @plugins = readdir SUBDIR;
  312.         closedir SUBDIR;
  313.         for my $plugin (@plugins) {
  314.             next if $plugin !~ /^[^.].*\.pl$/;
  315.             my $plugin_file = File::Spec->catfile($plugin_full_path,
  316.                               $plugin);
  317.             if (-f $plugin_file) {
  318.             $load_plugin->($plugin_file) or next PLUGIN;
  319.             }
  320.         }
  321.         }
  322.         }
  323.         closedir DH;
  324.     }
  325.     $mt;
  326. }
  327.  
  328. *mt_dir = \&server_path;
  329. sub server_path {
  330.     my $mt = shift;
  331.     $mt->{mt_dir};
  332. }
  333.  
  334. sub app_dir { $_[0]->{app_dir} }
  335.  
  336. sub rebuild {
  337.     my $mt = shift;
  338.     my %param = @_;
  339.     my $blog;
  340.     unless ($blog = $param{Blog}) {
  341.         my $blog_id = $param{BlogID};
  342.         $blog = MT::Blog->load($blog_id) or
  343.             return $mt->error(
  344.                 $mt->translate("Load of blog '[_1]' failed: [_2]",
  345.                     $blog_id, MT::Blog->errstr));
  346.     }
  347.     return 1 if $blog->is_dynamic;
  348.     my $at = $blog->archive_type || '';
  349.     my @at = split /,/, $at;
  350.     if (my $set_at = $param{ArchiveType}) {
  351.         my %at = map { $_ => 1 } @at;
  352.         return $mt->error(
  353.             $mt->translate("Archive type '[_1]' is not a chosen archive type",
  354.                 $set_at)) unless $at{$set_at};
  355.         @at = ($set_at);
  356.     }
  357.     if (@at) {
  358.         require MT::Entry;
  359.         my %arg = ('sort' => 'created_on', direction => 'descend');
  360.         if ($param{Limit}) {
  361.             $arg{offset} = $param{Offset};
  362.             $arg{limit} = $param{Limit};
  363.         }
  364.         my $iter = MT::Entry->load_iter({ blog_id => $blog->id,
  365.                       status => MT::Entry::RELEASE() },
  366.                     \%arg);
  367.         my $cb = $param{EntryCallback};
  368.         while (my $entry = $iter->()) {
  369.         if ($cb) {
  370.         $cb->($entry) || $mt->log($cb->errstr());
  371.         }
  372.             for my $at (@at) {
  373.                 if ($at eq 'Category') {
  374.                     my $cats = $entry->categories;
  375.                     for my $cat (@$cats) {
  376.                         $mt->_rebuild_entry_archive_type(
  377.                             Entry => $entry, Blog => $blog,
  378.                             Category => $cat, ArchiveType => 'Category',
  379.                             NoStatic => $param{NoStatic},
  380.                         ) or return;
  381.                     }
  382.                 } else {
  383.                     $mt->_rebuild_entry_archive_type( Entry => $entry,
  384.                                                       Blog => $blog,
  385.                                                       ArchiveType => $at,
  386.                                                       $param{TemplateID}
  387.                                                        ? (TemplateID =>
  388.                                                            $param{TemplateID})
  389.                                                        : (),
  390.                                                       NoStatic => $param{NoStatic})
  391.                         or return;
  392.                 }
  393.             }
  394.         }
  395.     }
  396.     unless ($param{NoIndexes}) {
  397.         $mt->rebuild_indexes( Blog => $blog ) or return;
  398.     }
  399.     if ($mt->{cfg}->PublishCommenterIcon) {
  400.         my $identity_link_image = $blog->site_path . "/nav-commenters.gif";
  401.         unless (-f $identity_link_image) {
  402.             my $nav_commenters_gif = (q{47494638396116000f00910200504d4b}.
  403.                                       q{ffffffffffff00000021f90401000002}.
  404.                                       q{002c0000000016000f0000022c948fa9}.
  405.                                       q{19e0bf2208b482a866a51723bd75dee1}.
  406.                                       q{70e2f83586837ed773a22fd4ba6cede2}.
  407.                                       q{241c8f7ceff9e95005003b});
  408.             $nav_commenters_gif = pack("H*", $nav_commenters_gif);
  409.             eval {
  410.                 if (open(TARGET, ">$identity_link_image")) {
  411.                     print TARGET $nav_commenters_gif;
  412.                     close TARGET;
  413.                 } else {
  414.                     $mt->log("Couldn't write to $identity_link_image");
  415.                     die;
  416.                 }
  417.             };
  418.         }
  419.     }
  420.     1;
  421. }
  422.  
  423. #   rebuild_entry
  424. #
  425. # $mt->rebuild_entry(Entry => $entry_id,
  426. #                    Blog => [ $blog | $blog_id ],
  427. #                    [ BuildDependencies => (0 | 1), ]
  428. #                    [ OldPrevious => $old_previous_entry_id,
  429. #                      OldNext => $old_next_entry_id, ]
  430. #                    [ NoStatic => (0 | 1), ]
  431. #                    );
  432. sub rebuild_entry {
  433.     my $mt = shift;
  434.     my %param = @_;
  435.     my $entry = $param{Entry} or
  436.         return $mt->error($mt->translate("Parameter '[_1]' is required",
  437.             'Entry'));
  438.     $entry = MT::Entry->load($entry) unless ref $entry;
  439.     my $blog;
  440.     unless ($blog = $param{Blog}) {
  441.         my $blog_id = $entry->blog_id;
  442.         $blog = MT::Blog->load($blog_id) or
  443.             return $mt->error($mt->translate("Load of blog '[_1]' failed: [_2]",
  444.                 $blog_id, MT::Blog->errstr));
  445.     }
  446.     return 1 if $blog->is_dynamic;
  447. #    MT::Util::start_background_task(sub {
  448.     my $at = $blog->archive_type;
  449.     if ($at && $at ne 'None') {
  450.         my @at = split /,/, $at;
  451.         for my $at (@at) {
  452.             if ($at eq 'Category') {
  453.                 my $cats = $entry->categories;   # (ancestors => 1)
  454.                 for my $cat (@$cats) {
  455.                     $mt->_rebuild_entry_archive_type(
  456.                         Entry => $entry, Blog => $blog,
  457.                         ArchiveType => $at, Category => $cat,
  458.                         NoStatic => $param{NoStatic}
  459.                     ) or return;
  460.                 }
  461.             } else {
  462.                 $mt->_rebuild_entry_archive_type( Entry => $entry,
  463.                                                   Blog => $blog,
  464.                                                   ArchiveType => $at,
  465.                                                   NoStatic => $param{NoStatic}
  466.                 ) or return;
  467.             }
  468.         }
  469.     }
  470.  
  471.     ## The above will just rebuild the archive pages for this particular
  472.     ## entry. If we want to rebuild all of the entries/archives/indexes
  473.     ## on which this entry could be featured etc., however, we need to
  474.     ## rebuild all of the entry's dependencies. Note that all of these
  475.     ## are not *necessarily* dependencies, depending on the usage of tags,
  476.     ## etc. There is not a good way to determine exact dependencies; it is
  477.     ## easier to just rebuild, rebuild, rebuild.
  478.  
  479.     return 1 unless $param{BuildDependencies};
  480.  
  481.     ## Rebuild previous and next entry archive pages.
  482.     if (my $prev = $entry->previous(1)) {
  483.         $mt->rebuild_entry( Entry => $prev ) or return;
  484.     }
  485.     if (my $next = $entry->next(1)) {
  486.         $mt->rebuild_entry( Entry => $next ) or return;
  487.     }
  488.  
  489.     ## Rebuild the old previous and next entries, if we have some.
  490.     if ($param{OldPrevious} && (my $old_prev = MT::Entry->load($param{OldPrevious}))) {
  491.         $mt->rebuild_entry( Entry => $old_prev ) or return;
  492.     }
  493.     if ($param{OldNext} && (my $old_next = MT::Entry->load($param{OldNext}))) {
  494.         $mt->rebuild_entry( Entry => $old_next ) or return;
  495.     }
  496.  
  497.     ## Rebuild all indexes, in case this entry is on an index.
  498.     $mt->rebuild_indexes( Blog => $blog ) or return;
  499.  
  500.     ## Rebuild previous and next daily, weekly, and monthly archives;
  501.     ## adding a new entry could cause changes to the intra-archive
  502.     ## navigation.
  503.     my %at = map { $_ => 1 } split /,/, $blog->archive_type;
  504.     for my $at (qw( Daily Weekly Monthly )) {
  505.         if ($at{$at}) {
  506.             my @arg = ($entry->created_on, $entry->blog_id, $at);
  507.             if (my $prev_arch = get_entry(@arg, 'previous')) {
  508.                 $mt->_rebuild_entry_archive_type(NoStatic => $param{NoStatic},
  509.                           Entry => $prev_arch,
  510.                           Blog => $blog,
  511.                           ArchiveType => $at) or return;
  512.             }
  513.             if (my $next_arch = get_entry(@arg, 'next')) {
  514.                 $mt->_rebuild_entry_archive_type(NoStatic => $param{NoStatic},
  515.                           Entry => $next_arch,
  516.                           Blog => $blog,
  517.                           ArchiveType => $at) or return;
  518.             }
  519.         }
  520.     }
  521. #    });
  522.  
  523.     1;
  524. }
  525.  
  526. sub _rebuild_entry_archive_type {
  527.     my $mt = shift;
  528.     my %param = @_;
  529.     my $at = $param{ArchiveType} or
  530.         return $mt->error($mt->translate("Parameter '[_1]' is required",
  531.             'ArchiveType'));
  532.     return 1 if $at eq 'None';
  533.     my $entry = ($param{ArchiveType} ne 'Category') ? ($param{Entry} or
  534.         return $mt->error($mt->translate("Parameter '[_1]' is required",
  535.             'Entry'))) : undef;
  536.     my $blog;
  537.     unless ($blog = $param{Blog}) {
  538.         my $blog_id = $entry->blog_id;
  539.         $blog = MT::Blog->load($blog_id) or
  540.             return $mt->error($mt->translate("Load of blog '[_1]' failed: [_2]",
  541.                 $blog_id, MT::Blog->errstr));
  542.     }
  543.  
  544.     ## Load the template-archive-type map entries for this blog and
  545.     ## archive type. We do this before we load the list of entries, because
  546.     ## we will run through the files and check if we even need to rebuild
  547.     ## anything. If there is nothing to rebuild at all for this entry,
  548.     ## we save some time by not loading the list of entries.
  549.     require MT::TemplateMap;
  550.     my @map;
  551.     if (my $maps = $mt->{__cached_maps}{$at . $blog->id}) {
  552.         @map = @$maps;
  553.     } else {
  554.         @map = MT::TemplateMap->load({ archive_type => $at,
  555.                                        blog_id => $blog->id,
  556.                                        $param{TemplateID}
  557.                                          ? (template_id => $param{TemplateID})
  558.                                          : ()
  559.                                      });
  560.         $mt->{__cached_maps}{$at . $blog->id} = \@map;
  561.     }
  562. #     return $mt->error($mt->translate(
  563. #         "You selected the archive type '[_1]', but you did not " .
  564. #         "define a template for this archive type.", $at)) unless @map;
  565.     return 1 unless @map;
  566.     my @map_build;
  567.  
  568. #     ## We keep a running total of the pages we have rebuilt
  569. #     ## in this session in $mt->{__rebuilt}.
  570. #     my $done = $mt->{__rebuilt};
  571.     for my $map (@map) {
  572.         my $file = archive_file_for($entry, $blog, $at, $param{Category}, $map);
  573.     if (!defined($file)) {
  574.         return $mt->error($mt->translate($blog->errstr()));
  575.     }
  576.         push @map_build, $map unless $mt->{done}->{$file};
  577.         $map->{__saved_output_file} = $file;
  578.     }
  579.     return 1 unless @map_build;
  580.     @map = @map_build;
  581.     
  582.     my(%cond);
  583.     require MT::Template::Context;
  584.     my $ctx = MT::Template::Context->new;
  585.     $ctx->{current_archive_type} = $at;
  586.     
  587.     $cond{IfAllowCommentHTML} = $blog->allow_comment_html;
  588.     $cond{IfRegistrationRequired} = $blog->allow_reg_comments
  589.                                  && !$blog->allow_unreg_comments;
  590.     $cond{IfCommentsAllowed} = $blog->allow_unreg_comments
  591.                              || $blog->allow_reg_comments;
  592.     $cond{IfRegistrationAllowed} = $blog->allow_reg_comments;
  593.     $cond{IfUnregisteredAllowed} = $blog->allow_unreg_comments;
  594.     $cond{IfDynamicComments} = 
  595.     $cond{IfDynamicCommentsStaticPage} = 
  596.     MT::ConfigMgr->instance()->DynamicComments;
  597.     $cond{IfNeedEmail} = $blog->require_comment_emails;
  598.     
  599.     $at ||= "";
  600.  
  601.     require MT::Promise;
  602.     import MT::Promise qw(delay);
  603.     
  604.     if ($at eq 'Individual') {
  605.         $ctx->stash('entry', $entry);
  606.         $ctx->{current_timestamp} = $entry->created_on;
  607.         $ctx->{modification_timestamp} = $entry->modified_on;
  608.         $cond{EntryIfAllowComments} = $entry->allow_comments;
  609.         $cond{EntryIfCommentsOpen} = $entry->allow_comments &&
  610.             $entry->allow_comments eq '1';
  611.         $cond{EntryIfAllowPings} = $entry->allow_pings;
  612.         $cond{EntryIfExtended} = $entry->text_more ? 1 : 0;
  613.     } elsif ($at eq 'Daily') {
  614.         my($start, $end) = start_end_day($entry->created_on, $blog);
  615.         $ctx->{current_timestamp} = $start;
  616.         $ctx->{current_timestamp_end} = $end;
  617.         my @entries = MT::Entry->load({ created_on => [ $start, $end ],
  618.                                         blog_id => $blog->id,
  619.                                         status => MT::Entry::RELEASE() },
  620.                                       { range_incl => { created_on => 1 } });
  621.         $ctx->stash('entries', delay(sub{\@entries}));
  622.     } elsif ($at eq 'Weekly') {
  623.         my($start, $end) = start_end_week($entry->created_on, $blog);
  624.         $ctx->{current_timestamp} = $start;
  625.         $ctx->{current_timestamp_end} = $end;
  626.         my @entries = MT::Entry->load({ created_on => [ $start, $end ],
  627.                                         blog_id => $blog->id,
  628.                                         status => MT::Entry::RELEASE() },
  629.                                       { range_incl => { created_on => 1 } });
  630.         $ctx->stash('entries', delay(sub{\@entries}));
  631.     } elsif ($at eq 'Monthly') {
  632.         my($start, $end) = start_end_month($entry->created_on, $blog);
  633.         $ctx->{current_timestamp} = $start;
  634.         $ctx->{current_timestamp_end} = $end;
  635.         my @entries = MT::Entry->load({ created_on => [ $start, $end ],
  636.                                         blog_id => $blog->id,
  637.                                         status => MT::Entry::RELEASE() },
  638.                                       { range_incl => { created_on => 1 } });
  639.         $ctx->stash('entries', delay(sub{\@entries}));
  640.     } elsif ($at eq 'Category') {
  641.         my $cat;
  642.         unless ($cat = $param{Category}) {
  643.             return $mt->error($mt->translate(
  644.                 "Building category archives, but no category provided."));
  645.         }
  646.         require MT::Placement;
  647.         $ctx->stash('archive_category', $cat);
  648.         my @entries = MT::Entry->load({ blog_id => $blog->id,
  649.                                         status => MT::Entry::RELEASE() },
  650.                          { 'join' => [ 'MT::Placement', 'entry_id',
  651.                                      { category_id => $cat->id } ] });
  652.         $ctx->stash('entries', delay(sub{\@entries}));
  653.     }
  654.     
  655.     $mt->{fmgr} = $blog->file_mgr;
  656.     my $arch_root = $blog->archive_path;
  657.     return $mt->error($mt->translate("You did not set your Local Archive Path"))
  658.         unless $arch_root;
  659.     
  660.     my ($start, $end) = ($at ne 'Category') ? 
  661.         start_end_period($at, $entry->created_on) : ();
  662.     
  663.     ## For each mapping, we need to rebuild the entries we loaded above in
  664.     ## the particular template map, and write it to the specified archive
  665.     ## file template.
  666.     require MT::Template;
  667.     for my $map (@map) {
  668.         $mt->rebuild_file($blog, $arch_root, $map, $at, $ctx, \%cond,
  669.                           !$param{NoStatic},
  670.                           Category => $param{Category},
  671.                           Entry => $entry,
  672.                           StartDate => $start,
  673.                           ) or return;
  674.     }
  675.     1;
  676. }
  677.  
  678. sub rebuild_file {
  679.     my $mt = shift;
  680.     my ($blog, $arch_root, $map, $at, $ctx, $cond, 
  681.         $build_static, %specifier) = @_;
  682.  
  683.     my $entry;
  684.     my $start;
  685.     my $category;
  686.     if ($at eq 'Category') {
  687.         $category = $specifier{Category};
  688.         die "Category archive type requires Category parameter"
  689.             unless $specifier{Category};
  690.         $category = MT::Category->load($category) unless ref $category;
  691.     } elsif ($at eq 'Individual') {
  692.         $entry = $specifier{Entry};
  693.         $entry = MT::Entry->load($entry) if !ref $entry;
  694.         die "Individual archive type requires Entry parameter"
  695.             unless $specifier{Entry};
  696.     } else {
  697.         # Date-based archive type
  698.         $start = $specifier{StartDate};
  699.         die "Date-based archive types require StartDate parameter" 
  700.             unless $specifier{StartDate};
  701.     }
  702.     my $fmgr = $mt->{fmgr};
  703.     {   # (silly ruse to make the diff more transparent.)
  704.         # Calculate file path and URL for the new entry.
  705.         my $file = File::Spec->catfile($arch_root, $map->{__saved_output_file});
  706.         my $url = $blog->archive_url;
  707.         $url .= '/' unless $url =~ m|/$|;
  708.         $url .= $map->{__saved_output_file};
  709.         
  710.         my $tmpl = $mt->{__cached_templates}{$map->template_id};
  711.         unless ($tmpl) {
  712.             $tmpl = MT::Template->load($map->template_id);
  713.             if ($mt->{cache_templates}) {
  714.                 $mt->{__cached_templates}{$tmpl->id} = $tmpl;
  715.             }
  716.         }
  717.  
  718.         ## Untaint. We have to assume that we can trust the user's setting of
  719.         ## the archive_path, and nothing else is based on user input.
  720.         ($file) = $file =~ /(.+)/s;
  721.         
  722.         my ($rel_url) = ($url =~ m|^(?:[^:]*\:\/\/)?[^/]*(.*)|);
  723.         $rel_url =~ s|//+|/|g;
  724.         my $finfo;
  725.  
  726.         my $needs_fileinfo = $blog->needs_fileinfo;
  727.         if ($needs_fileinfo) {
  728.             # Clear out all the FileInfo records that might point at the page 
  729.             # we're about to create
  730.             # MBE: select based on target file rather than source specifier?
  731.             # FYI: if it's an individual entry, we don't use the date as a 
  732.             #      criterion, since this could actually have changed since
  733.             #      the FileInfo was last built.
  734.             # Note: when the date does change, the old date-based archive
  735.             #      doesn't necessarily get fixed, but if another comes along
  736.             #      it will get corrected
  737.             require MT::FileInfo;
  738.             my @finfos = MT::FileInfo->load({ blog_id => $blog->id,
  739.                                               ($at eq 'Category') ?
  740.                                                   (category_id =>
  741.                                                       $category->id)
  742.                                                 : (),
  743.                                               ($at eq 'Individual') ?
  744.                                                   (entry_id => $entry->id) :
  745.                                                   ($at ne 'Category') ?
  746.                                                       (startdate => $start) : (),
  747.                                               templatemap_id => $map->id,
  748.                                               archive_type => $at,
  749.                                               });
  750.  
  751.             if ((scalar @finfos == 1) && ($finfos[0]->file_path eq $file)) {
  752.                 $finfo = $finfos[0];
  753.             } else {
  754.                 for my $finfo (@finfos) {
  755.                     $finfo->remove();
  756.                 }
  757.                 
  758.                 $finfo = MT::FileInfo->set_info_for_url($rel_url, $file, $at,
  759.                                                   { Blog => $blog->id,
  760.                                                     TemplateMap => $map->id,
  761.                                                     Template => $map->template_id,
  762.                                                     ($at eq 'Individual' && $entry)
  763.                                                         ? (Entry => $entry->id): (),
  764.                                                         StartDate => $start,
  765.                                                         ($at eq 'Category' && $category)
  766.                                                         ? (Category => $category->id) : (),
  767.                                                    })
  768.                 || die "Couldn't create FileInfo because " . MT::FileInfo->errstr();
  769.             }
  770.  
  771.             # If you rebuild when you've just switched to dynamic pages,
  772.             # we move the file that might be there so that the custom
  773.             # 404 will be triggered.
  774.             if ($tmpl->build_dynamic) {
  775.                 rename($finfo->file_path,
  776.                        $finfo->file_path . '.static');
  777.                 $finfo->virtual(1); $finfo->save();
  778.             }
  779.         }
  780.         if (!$tmpl->build_dynamic) {
  781.             if ( $build_static &&
  782.                  MT->run_callbacks('BuildFileFilter', Context => $ctx,
  783.                                    ArchiveType => $at, TemplateMap => $map, 
  784.                                    Blog => $blog, Entry => $entry,
  785.                                    FileInfo => $finfo,
  786.                                    PeriodStart => $start, Category => $category))
  787.             {
  788.                 my $html = undef;
  789.                 $html = $tmpl->build($ctx, $cond);
  790.                 defined($html) or
  791.                     return $mt->error(($category ?
  792.                                       $mt->translate("Building category '[_1]' failed: [_2]",
  793.                                                      $category->id, $tmpl->errstr) :
  794.                                       $entry ?
  795.                                       $mt->translate("Building entry '[_1]' failed: [_2]",
  796.                                                      $entry->title, $tmpl->errstr):
  797.                                       $mt->translate("Building date-based archive '[_1]' failed: [_2]", $at . $start, $tmpl->errstr)));
  798.                 ## First check whether the content is actually
  799.                 ## changed. If not, we won't update the published
  800.                 ## file, so as not to modify the mtime.  
  801.                 $mt->{done}->{$map->{__saved_output_file}}++, next
  802.                     unless $fmgr->content_is_updated($file, \$html);
  803.  
  804.                 ## Determine if we need to build directory structure,
  805.                 ## and build it if we do. DirUmask determines
  806.                 ## directory permissions.
  807.                 my $path = dirname($file);
  808.                 $path =~ s!/$!!;  ## OS X doesn't like / at the end in mkdir().
  809.                 unless ($fmgr->exists($path)) {
  810.                     $fmgr->mkpath($path)
  811.                         or return $mt->trans_error("Error making path '[_1]': [_2]",
  812.                                                    $path, $fmgr->errstr);
  813.                 }
  814.                 
  815.                 ## By default we write all data to temp files, then rename
  816.                 ## the temp files to the real files (an atomic
  817.                 ## operation). Some users don't like this (requires too
  818.                 ## liberal directory permissions). So we have a config
  819.                 ## option to turn it off (NoTempFiles).
  820.                 my $use_temp_files = !$mt->{cfg}->NoTempFiles;
  821.                 my $temp_file = $use_temp_files ? "$file.new" : $file;
  822.                 defined($fmgr->put_data($html, $temp_file))
  823.                     or return $mt->trans_error("Writing to '[_1]' failed: [_2]",
  824.                                                $temp_file, $fmgr->errstr);
  825.                 if ($use_temp_files) {
  826.                     $fmgr->rename($temp_file, $file)
  827.                         or return $mt->trans_error("Renaming tempfile '[_1]' failed: [_2]",
  828.                                                    $temp_file, $fmgr->errstr);
  829.                 }
  830.                 if ($finfo) {
  831.                     $finfo->virtual(0); $finfo->save();
  832.                 }
  833.                 MT->run_callbacks('BuildFile', Context => $ctx,
  834.                                   ArchiveType => $at, TemplateMap => $map,
  835.                                   FileInfo => $finfo, Blog => $blog,
  836.                                   Entry => $entry, PeriodStart => $start,
  837.                                   Category => $category, File => $file);
  838.             }
  839.         }
  840.         $mt->{done}->{$map->{__saved_output_file}}++;
  841.     }
  842.     1;
  843. }
  844.  
  845. sub rebuild_indexes {
  846.     my $mt = shift;
  847.     my %param = @_;
  848.     require MT::Template;
  849.     require MT::Template::Context;
  850.     require MT::Entry;
  851.     my $blog;
  852.     unless ($blog = $param{Blog}) {
  853.         my $blog_id = $param{BlogID};
  854.         $blog = MT::Blog->load($blog_id) or
  855.             return $mt->error($mt->translate("Load of blog '[_1]' failed: [_2]",
  856.                 $blog_id, MT::Blog->errstr));
  857.     }
  858.     return 1 if $blog->is_dynamic;
  859.     my $iter;
  860.     if (my $tmpl = $param{Template}) {
  861.         my $i = 0;
  862.         $iter = sub { $i++ < 1 ? $tmpl : undef };
  863.     } else {
  864.         $iter = MT::Template->load_iter({ type => 'index',
  865.             blog_id => $blog->id });
  866.     }
  867.     local *FH;
  868.     my $site_root = $blog->site_path;
  869.     return $mt->error($mt->translate("You did not set your Local Site Path"))
  870.         unless $site_root;
  871.     my $fmgr = $blog->file_mgr;
  872.     while (my $tmpl = $iter->()) {
  873.         ## Skip index templates that the user has designated not to be
  874.         ## rebuilt automatically. We need to do the defined-ness check
  875.         ## because we added the flag in 2.01, and for templates saved
  876.         ## before that time, the rebuild_me flag will be undefined. But
  877.         ## we assume that these templates should be rebuilt, since that
  878.         ## was the previous behavior.
  879.         next if !$param{Force} && (
  880.                 (defined $tmpl->rebuild_me && !$tmpl->rebuild_me)
  881.                                    && !$tmpl->build_dynamic);
  882.  
  883.         my $index = $tmpl->outfile
  884.             or return $mt->error($mt->translate(
  885.                 "Template '[_1]' does not have an Output File.", $tmpl->name));
  886.         my $url = join('/', $blog->site_url, $index);
  887.         unless (File::Spec->file_name_is_absolute($index)) {
  888.             $index = File::Spec->catfile($site_root, $index);
  889.         }
  890.         my ($rel_url) = ($url =~ m|^(?:[^:]*\:\/\/)?[^/]*(.*)|);
  891.         $rel_url =~ s|//+|/|g;
  892.         my $needs_fileinfo = $blog->needs_fileinfo;
  893.         my $finfo;
  894.         if ($needs_fileinfo) {
  895.             require MT::FileInfo;
  896.             my @finfos = MT::FileInfo->load({blog_id => $tmpl->blog_id,
  897.                                              template_id => $tmpl->id,
  898.                                          } );
  899.             if (@finfos == 1 && $finfos[0]->file_path eq $index) {
  900.                 $finfo = $finfos[0];
  901.             } else {
  902.                 foreach ( @finfos ) { $_->remove(); }
  903.                 $finfo = MT::FileInfo->set_info_for_url($rel_url,$index, 'index',
  904.                                                         { Blog => $tmpl->blog_id,
  905.                                                           Template => $tmpl->id,
  906.                                                       })
  907.                     || die "Couldn't create FileInfo because " . 
  908.                     MT::FileInfo->errstr();
  909.             }
  910.             if ($tmpl->build_dynamic) {
  911.                 rename($index, $index . ".static");
  912.             }
  913.         }
  914.  
  915.         ## Untaint. We have to assume that we can trust the user's setting of
  916.         ## the site_path and the template outfile.
  917.         ($index) = $index =~ /(.+)/s;
  918.  
  919.         if (!$tmpl->build_dynamic) {
  920.             my $ctx = MT::Template::Context->new;
  921.             if (MT->run_callbacks('BuildFileFilter', Context => $ctx,
  922.                                    ArchiveType => 'index',
  923.                                    Blog => $blog, FileInfo => $finfo,
  924.                                    File => $index)) {
  925.                 my $html = $tmpl->build($ctx);
  926.                 return $mt->error( $tmpl->errstr ) unless defined $html;
  927.  
  928.                 ## First check whether the content is actually changed. If not,
  929.                 ## we won't update the published file, so as not to modify the mtime.
  930.                 next unless $fmgr->content_is_updated($index, \$html);
  931.  
  932.                 ## Update the published file.
  933.                 my $use_temp_files = !$mt->{cfg}->NoTempFiles;
  934.                 my $temp_file = $use_temp_files ? "$index.new" : $index;
  935.                 defined($fmgr->put_data($html, $temp_file))
  936.                     or return $mt->trans_error("Writing to '[_1]' failed: [_2]",
  937.                                            $temp_file, $fmgr->errstr);
  938.                 if ($use_temp_files) {
  939.                     $fmgr->rename($temp_file, $index)
  940.                         or return $mt->trans_error("Renaming tempfile '[_1]' failed: [_2]",
  941.                                                    $temp_file, $fmgr->errstr);
  942.                 }
  943.                 MT->run_callbacks('BuildFile', Context => $ctx,
  944.                                   ArchiveType => 'index',
  945.                                   FileInfo => $finfo, Blog => $blog,
  946.                                   File => $index);
  947.             }
  948.         }
  949.     }
  950.     1;
  951. }
  952.  
  953. sub ping {
  954.     my $mt = shift;
  955.     my %param = @_;
  956.     my $blog;
  957.     unless ($blog = $param{Blog}) {
  958.         my $blog_id = $param{BlogID};
  959.         $blog = MT::Blog->load($blog_id) or
  960.             return $mt->error(
  961.                 $mt->translate("Load of blog '[_1]' failed: [_2]",
  962.                     $blog_id, MT::Blog->errstr));
  963.     }
  964.  
  965.     my(@res);
  966.  
  967.     my $send_updates = 1;
  968.     if (exists $param{OldStatus}) {
  969.         ## If this is a new entry (!$old_status) OR the status was previously
  970.         ## set to draft, and is now set to publish, send the update pings.
  971.         my $old_status = $param{OldStatus};
  972.         if ($old_status && $old_status eq MT::Entry::RELEASE()) {
  973.             $send_updates = 0;
  974.         }
  975.     }
  976.  
  977.     if ($send_updates) {
  978.         ## Send update pings.
  979.         my @updates = $mt->update_ping_list($blog);
  980.         for my $url (@updates) {
  981.             require MT::XMLRPC;
  982.             if (MT::XMLRPC->ping_update('weblogUpdates.ping', $blog, $url)) {
  983.                 push @res, { good => 1, url => $url, type => "update" };
  984.             } else {
  985.                 push @res, { good => 0, url => $url, type => "update",
  986.                              error => MT::XMLRPC->errstr };
  987.             }
  988.         }
  989.         if ($blog->mt_update_key) {
  990.             require MT::XMLRPC;
  991.             if (MT::XMLRPC->mt_ping($blog)) {
  992.                 push @res, { good => 1, url => $mt->{cfg}->MTPingURL,
  993.                              type => "update" };
  994.             } else {
  995.                 push @res, { good => 0, url => $mt->{cfg}->MTPingURL,
  996.                              type => "update", error => MT::XMLRPC->errstr };
  997.             }
  998.         }
  999.     }
  1000.  
  1001.     ## Send TrackBack pings.
  1002.     if (my $entry = $param{Entry}) {
  1003.         my $pings = $entry->to_ping_url_list;
  1004.  
  1005.         my %pinged = map { $_ => 1 } @{ $entry->pinged_url_list };
  1006.         my $cats = $entry->categories;
  1007.         for my $cat (@$cats) {
  1008.             push @$pings, grep !$pinged{$_}, @{ $cat->ping_url_list };
  1009.         }
  1010.  
  1011.         my $ua = MT->new_ua;
  1012.  
  1013.         ## Build query string to be sent on each ping.
  1014.         my @qs;
  1015.         push @qs, 'title=' . MT::Util::encode_url($entry->title);
  1016.         push @qs, 'url=' . MT::Util::encode_url($entry->permalink);
  1017.         push @qs, 'excerpt=' . MT::Util::encode_url($entry->get_excerpt);
  1018.         push @qs, 'blog_name=' . MT::Util::encode_url($blog->name);
  1019.         my $qs = join '&', @qs;
  1020.  
  1021.         ## Character encoding--best guess. Default to iso-8859-1, just as we
  1022.         ## do in MT::Template::Context::_hdlr_publish_charset.
  1023.         my $enc = $mt->{cfg}->PublishCharset || 'iso-8859-1';
  1024.  
  1025.         for my $url (@$pings) {
  1026.             $url =~ s/^\s*//;
  1027.             $url =~ s/\s*$//;
  1028.             my $req = HTTP::Request->new(POST => $url);
  1029.         $req->content_type("application/x-www-form-urlencoded; charset=$enc");
  1030.         $req->content($qs);
  1031.             my $res = $ua->request($req);
  1032.             if (substr($res->code, 0, 1) eq '2') {
  1033.                 my $c = $res->content;
  1034.                 my($error, $msg) = $c =~
  1035.                     m!<error>(\d+).*<message>(.+?)</message>!s;
  1036.                 if ($error) {
  1037.                     push @res, { good => 0, url => $url, type => 'trackback',
  1038.                                  error => $msg };
  1039.                 } else {
  1040.                     push @res, { good => 1, url => $url, type => 'trackback' };
  1041.                 }
  1042.             } else {
  1043.                 push @res, { good => 0, url => $url, type => 'trackback',
  1044.                              error => "HTTP error: " . $res->status_line };
  1045.             }
  1046.         }
  1047.     }
  1048.     \@res;
  1049. }
  1050.  
  1051. sub ping_and_save {
  1052.     my $mt = shift;
  1053.     my %param = @_;
  1054.     if (my $entry = $param{Entry}) {
  1055.         my $results = $mt->ping(@_) or return;
  1056.         my %still_ping;
  1057.         my $pinged = $entry->pinged_url_list;
  1058.         for my $res (@$results) {
  1059.             next if $res->{type} ne 'trackback';
  1060.             if (!$res->{good}) {
  1061.                 $still_ping{ $res->{url} } = 1;
  1062.             } else {
  1063.                 push @$pinged, $res->{url};
  1064.             }
  1065.         }
  1066.         $entry->pinged_urls(join "\n", @$pinged);
  1067.         $entry->to_ping_urls(join "\n", keys %still_ping);
  1068.         $entry->save or return $mt->error($entry->errstr);
  1069.         return $results;
  1070.     }
  1071.     1;
  1072. }
  1073.  
  1074. sub needs_ping {
  1075.     my $mt = shift;
  1076.     my %param = @_;
  1077.     my $blog = $param{Blog};
  1078.     my $entry = $param{Entry};
  1079.     return unless $entry->status == MT::Entry::RELEASE();
  1080.     my $old_status = $param{OldStatus};
  1081.     my %list;
  1082.     ## If this is a new entry (!$old_status) OR the status was previously
  1083.     ## set to draft, and is now set to publish, send the update pings.
  1084.     if (!$old_status || $old_status ne MT::Entry::RELEASE()) {
  1085.         my @updates = $mt->update_ping_list($blog);
  1086.         @list{ @updates } = (1) x @updates;
  1087.         $list{$mt->{cfg}->MTPingURL} = 1 if $blog && $blog->mt_update_key;
  1088.     }
  1089.     if ($entry) {
  1090.         @list{ @{ $entry->to_ping_url_list } } = ();
  1091.         my %pinged = map { $_ => 1 } @{ $entry->pinged_url_list };
  1092.         my $cats = $entry->categories;
  1093.         for my $cat (@$cats) {
  1094.             @list{ grep !$pinged{$_}, @{ $cat->ping_url_list } } = ();
  1095.         }
  1096.     }
  1097.     my @list = keys %list;
  1098.     return unless @list;
  1099.     \@list;
  1100. }
  1101.  
  1102. sub update_ping_list {
  1103.     my $mt = shift;
  1104.     my($blog) = @_;
  1105.     my @updates;
  1106.     if ($blog->ping_weblogs) {
  1107.         push @updates, $mt->{cfg}->WeblogsPingURL;
  1108.     }
  1109.     if ($blog->ping_blogs) {
  1110.         push @updates, $mt->{cfg}->BlogsPingURL;
  1111.     }
  1112.     if ($blog->ping_technorati) {
  1113.         push @updates, $mt->{cfg}->TechnoratiPingURL;
  1114.     }
  1115.     if (my $others = $blog->ping_others) {
  1116.         push @updates, split /\r?\n/, $others;
  1117.     }
  1118.     my %updates;
  1119.     for my $url (@updates) {
  1120.         for ($url) {
  1121.             s/^\s*//; s/\s*$//;
  1122.         }
  1123.         next unless $url =~ /\S/;
  1124.         $updates{$url}++;
  1125.     }
  1126.     keys %updates;
  1127. }
  1128.  
  1129. {
  1130.     my $LH;
  1131.     sub set_language {
  1132.         require MT::L10N;
  1133.         $LH = MT::L10N->get_handle($_[1]);
  1134.     }
  1135.  
  1136.     sub translate {
  1137.     my $this = shift;
  1138.         my $phrase = $LH->maketext(@_);
  1139.     my $enc = MT::ConfigMgr->instance->PublishCharset || $LH->encoding;
  1140.          if ($enc ne 'utf-8' && $] > 5.007003 && $phrase =~ m/[x7F-xFF]/) {
  1141.              ## Language dictionaries are encoded in utf-8; need to transcode.
  1142.              eval {require Encode;};
  1143.          unless ($@){
  1144.             Encode::from_to($phrase, 'utf-8', $enc);
  1145.          }
  1146.          }
  1147.          $phrase;
  1148.     }
  1149.  
  1150.     sub translate_templatized {
  1151.         my $mt = shift;
  1152.         my($text) = @_;
  1153.         $text =~ s!<MT_TRANS ([^>]+)>!
  1154.             my($msg, %args) = ($1);
  1155.             while ($msg =~ /(\w+)\s*=\s*(["'])(.*?)\2/g) {  #"
  1156.                 $args{$1} = $3;
  1157.             }
  1158.             $args{params} = '' unless defined $args{params};
  1159.             my @p = map MT::Util::decode_html($_),
  1160.                     split /\s*%%\s*/, $args{params};
  1161.             @p = ('') unless @p;
  1162.             my $translation = $mt->translate($args{phrase}, @p);
  1163.             $translation =~ s/([\\'])/\\$1/sg if $args{escape};
  1164.             $translation;
  1165.         !ge;
  1166.         $text;
  1167.     }
  1168.  
  1169.     sub current_language { $LH->language_tag }
  1170.     sub language_handle { $LH }
  1171. }
  1172.  
  1173. sub supported_languages {
  1174.     my $mt = shift;
  1175.     require MT::L10N;
  1176.     require File::Basename;
  1177.     ## Determine full path to lib/MT/L10N directory...
  1178.     my $lib = 
  1179.         File::Spec->catdir(File::Basename::dirname($INC{'MT/L10N.pm'}), 'L10N');
  1180.     ## ... From that, determine full path to extlib/MT/L10N.
  1181.     ## To do that, we look for the last instance of the string 'lib'
  1182.     ## in $lib and replace it with 'extlib'. reverse is a nice tricky
  1183.     ## way of doing that.
  1184.     (my $extlib = reverse $lib) =~ s!bil!biltxe!;
  1185.     $extlib = reverse $extlib;
  1186.     my @dirs = ( $lib, $extlib );
  1187.     my %langs;
  1188.     for my $dir (@dirs) {
  1189.         opendir DH, $dir or next;
  1190.         for my $f (readdir DH) {
  1191.             my($tag) = $f =~ /^(\w+)\.pm$/;
  1192.             next unless $tag;
  1193.             my $lh = MT::L10N->get_handle($tag);
  1194.             $langs{$lh->language_tag} = $lh->language_name;
  1195.         }
  1196.         closedir DH;
  1197.     } 
  1198.     \%langs;
  1199. }
  1200.  
  1201. # For your convenience
  1202. sub trans_error {
  1203.     my $app = shift;
  1204.     $app->error($app->translate(@_));
  1205. }
  1206.  
  1207. sub add_text_filter {
  1208.     my $mt = shift;
  1209.     my($key, $cfg) = @_;
  1210.     $cfg->{label} ||= $key;
  1211.     return $mt->error("No executable code") unless $cfg->{on_format};
  1212.     $Text_filters{$key} = $cfg;
  1213. }
  1214.  
  1215. sub all_text_filters { \%Text_filters }
  1216.  
  1217. sub apply_text_filters {
  1218.     my $mt = shift;
  1219.     my($str, $filters, @extra) = @_;
  1220.     for my $filter (@$filters) {
  1221.         next unless $Text_filters{$filter};
  1222.         $str = $Text_filters{$filter}{on_format}->($str, @extra);
  1223.     }
  1224.     $str;
  1225. }
  1226.  
  1227. sub new_ua {
  1228.     my $class = shift;
  1229.     require LWP::UserAgent;
  1230.     my $cfg = MT::ConfigMgr->instance;
  1231.     if (my $localaddr = $cfg->PingInterface) {
  1232.         @LWP::Protocol::http::EXTRA_SOCK_OPTS = (
  1233.               LocalAddr => $localaddr,
  1234.               Reuse => 1 );
  1235.     }
  1236.     my $ua = LWP::UserAgent->new;
  1237.     $ua->max_size(100_000) if $ua->can('max_size');
  1238.     $ua->agent('MovableType/' . MT->VERSION);
  1239.     $ua->timeout($cfg->PingTimeout);
  1240.     if (my $proxy = $cfg->PingProxy) {
  1241.         $ua->proxy(http => $proxy);
  1242.         my @domains = split(/,\s*/, $cfg->PingNoProxy);
  1243.         $ua->no_proxy(@domains);
  1244.     }
  1245.     $ua;        
  1246. }
  1247.  
  1248. sub build_email {
  1249.     my $class = shift;
  1250.     my($file, $param) = @_;
  1251.     my $cfg = MT::ConfigMgr->instance;
  1252.     my @paths = (File::Spec->catdir($cfg->TemplatePath, 'email'));
  1253.     require HTML::Template;
  1254.     my $tmpl;
  1255.     eval {
  1256.         local $1; ## This seems to fix a utf8 bug (of course).
  1257.         $tmpl = HTML::Template->new_file(
  1258.             $file,
  1259.             path => \@paths,
  1260.             search_path_on_include => 1,
  1261.             die_on_bad_params => 0,
  1262.             global_vars => 1);
  1263.     };
  1264.     return $class->error("Loading template '$file' failed: $@") if $@;
  1265.     for my $key (keys %$param) {
  1266.         $tmpl->param($key, $param->{$key});
  1267.     }
  1268.     $class->translate_templatized($tmpl->output);
  1269. }
  1270.  
  1271. use MT::Entry 'FUTURE';
  1272.  
  1273. sub get_next_sched_post_for_user {
  1274.     my ($author_id, @further_blog_ids) = @_;
  1275.     require MT::Permission;
  1276.     my @perms = MT::Permission->load({author_id => $author_id}, {});
  1277.     my @blogs = @further_blog_ids;
  1278.     for my $perm (@perms) {
  1279.     next unless ($perm->can_edit_config
  1280.              || $perm->can_post
  1281.              || $perm->can_edit_all_posts);
  1282.     push @blogs, $perm->blog_id;
  1283.     }
  1284.     my $next_sched_utc = undef;
  1285.     for my $blog_id (@blogs) {
  1286.     my $blog = MT::Blog->load($blog_id);
  1287.     my $earliest_entry = MT::Entry->load({status => MT::Entry::FUTURE,
  1288.                           blog_id => $blog_id},
  1289.                          {sort => 'created_on'});
  1290.     if ($earliest_entry) {
  1291.         my $entry_utc =MT::Util::ts2iso($blog,$earliest_entry->created_on);
  1292.         if ($entry_utc < $next_sched_utc || !defined($next_sched_utc))
  1293.         {
  1294.         $next_sched_utc = $entry_utc;
  1295.         }
  1296.     }
  1297.     }
  1298.     return $next_sched_utc;
  1299. }
  1300.  
  1301. 1;
  1302. __END__
  1303.  
  1304. =head1 NAME
  1305.  
  1306. MT - Movable Type
  1307.  
  1308. =head1 SYNOPSIS
  1309.  
  1310.     use MT;
  1311.     my $mt = MT->new;
  1312.     $mt->rebuild(BlogID => 1)
  1313.         or die $mt->errstr;
  1314.  
  1315. =head1 DESCRIPTION
  1316.  
  1317. The I<MT> class is the main high-level rebuilding/pinging interface in the
  1318. Movable Type library. It handles all rebuilding operations. It does B<not>
  1319. handle any of the application functionality--for that, look to I<MT::App> and
  1320. I<MT::App::CMS>, both of which subclass I<MT> to handle application requests.
  1321.  
  1322. =head1 PLUGIN APPLICATIONS
  1323.  
  1324. At any given time, the user of the Movable Type platform is
  1325. interacting with either the core Movable Type application, or a plugin
  1326. application (or "sub-application").
  1327.  
  1328. A plugin application is a plugin with a user interface that inherits
  1329. functionality from Movable Type, and appears to the user as a
  1330. component of Movable Type. A plugin application typically has its own
  1331. templates displaying its own special features; but it inherits some
  1332. templates from Movable Type, such as the navigation chrome and error
  1333. pages.
  1334.  
  1335. =head2 The MT Root and the Application Root
  1336.  
  1337. To locate assets of the core Movable Type application and any plugin
  1338. applications, the platform uses two directory paths, C<mt_dir> and
  1339. C<app_dir>. These paths are returned by the MT class methods with the
  1340. same names, and some other methods return derivatives of these paths.
  1341.  
  1342. Conceptually, mt_dir is the root of the Movable Type installation, and
  1343. app_dir is the root of the "currently running application", which
  1344. might be Movable Type or a plugin application. It is important to
  1345. understand the distinction between these two values and what each is
  1346. used for.
  1347.  
  1348. The I<mt_dir> is the absolute path to the directory where MT itself is
  1349. located. Most importantly, the F<mt.cfg> file and the CGI scripts that
  1350. bootstrap an MT request are found here. This directory is also the
  1351. default base path under which MT's core templates are found (but this
  1352. can be overridden using the I<TemplatePath> configuration setting).
  1353.  
  1354. Likewise, the I<app_dir> is the directory where the "current"
  1355. application's assets are rooted. The platform will search for
  1356. application templates underneath the I<app_dir>, but this search also
  1357. searches underneath the I<mt_dir>, allowing the application to make
  1358. use of core headers, footers, error pages, and possibly other
  1359. templates.
  1360.  
  1361. In order for this to be useful, the plugin's templates and
  1362. code should all be located underneath the same directory. The relative
  1363. path from the I<app_dir> to the application's templates is
  1364. configurable. For details on how to indicate the location of your
  1365. plugin's templates, see L<MT::App>.
  1366.  
  1367. =head2 Finding the Root Paths
  1368.  
  1369. When a plugin application initializes its own application class (a
  1370. subclass of MT::App), the I<mt_dir> should be discovered and passed
  1371. constructor. This comes either from the C<Directory> parameter or the
  1372. C<Config> parameter.
  1373.  
  1374. Since plugins are loaded from a descendent of the MT root directory,
  1375. the plugin bootstrap code can discover the F<mt.cfg> file (and thus
  1376. the MT root directory) by traversing the filesystem; the absolute path
  1377. to that file can be passed as the C<Config> parameter to
  1378. MT::App::new. Working code to do this can be found in the
  1379. examples/plugins/mirror/mt-mirror.cgi file.
  1380.  
  1381. The I<app_dir>, on the other hand, always derives from the location of
  1382. the currently-running program, so it typically does not need to be
  1383. specified.
  1384.  
  1385. =head1 USAGE
  1386.  
  1387. I<MT> has the following interface. On failure, all methods return C<undef>
  1388. and set the I<errstr> for the object or class (depending on whether the
  1389. method is an object or class method, respectively); look below at the section
  1390. L<ERROR HANDLING> for more information.
  1391.  
  1392. =head2 MT->new( %args )
  1393.  
  1394. Constructs a new I<MT> instance and returns that object. Returns C<undef>
  1395. on failure.
  1396.  
  1397. I<new> will also read your F<mt.cfg> file (provided that it can find it--if
  1398. you find that it can't, take a look at the I<Config> directive, below). It
  1399. will also initialize the chosen object driver; the default is the C<DBM>
  1400. object driver.
  1401.  
  1402. I<%args> can contain:
  1403.  
  1404. =over 4
  1405.  
  1406. =item * Config
  1407.  
  1408. Path to the F<mt.cfg> file.
  1409.  
  1410. If you do not specify a path, I<MT> will try to find your F<mt.cfg> file
  1411. in the current working directory.
  1412.  
  1413. =back
  1414.  
  1415. =head2 MT->instance
  1416.  
  1417. MT and all it's subclasses are now singleton classes, meaning you can only
  1418. have one instance. MT->instance() returns that one instance. MT->new() is
  1419. now an alias to instance.
  1420.  
  1421. =head2 MT->unplug
  1422.  
  1423. Removes the global reference to the MT instance.
  1424.  
  1425. =head2 MT::log( $message ) or $mt->log( $message )
  1426.  
  1427. Adds an entry to the application's log table. Also writes message to
  1428. STDERR which is typically routed to the web server's error log.
  1429.  
  1430. =head2 $mt->server_path, $mt->mt_dir
  1431.  
  1432. Both of these methods return the physical file path to the directory
  1433. that is the home of the MT installation. This would be the value of
  1434. the 'Directory' parameter given in the MT constructor, or would be
  1435. determined based on the path of the configuration file.
  1436.  
  1437. =head2 $mt->app_dir
  1438.  
  1439. Returns the physical file path to the active application directory. This
  1440. is determined by the directory of the active script.
  1441.  
  1442. =head2 $mt->rebuild( %args )
  1443.  
  1444. Rebuilds your entire blog, indexes and archives; or some subset of your blog,
  1445. as specified in the arguments.
  1446.  
  1447. I<%args> can contain:
  1448.  
  1449. =over 4
  1450.  
  1451. =item * Blog
  1452.  
  1453. An I<MT::Blog> object corresponding to the blog that you would like to
  1454. rebuild.
  1455.  
  1456. Either this or C<BlogID> is required.
  1457.  
  1458. =item * BlogID
  1459.  
  1460. The ID of the blog that you would like to rebuild.
  1461.  
  1462. Either this or C<Blog> is required.
  1463.  
  1464. =item * ArchiveType
  1465.  
  1466. The archive type that you would like to rebuild. This should be one of the
  1467. following values: C<Individual>, C<Daily>, C<Weekly>, C<Monthly>, or
  1468. C<Category>.
  1469.  
  1470. This argument is optional; if not provided, all archive types will be rebuilt.
  1471.  
  1472. =item * EntryCallback
  1473.  
  1474. A callback that will be called for each entry that is rebuilt. If provided,
  1475. the value should be a subroutine reference; the subroutine will be handed
  1476. the I<MT::Entry> object for the entry that is about to be rebuilt. You could
  1477. use this to keep a running log of which entry is being rebuilt, for example:
  1478.  
  1479.     $mt->rebuild(
  1480.               BlogID => $blog_id,
  1481.               EntryCallback => sub { print $_[0]->title, "\n" },
  1482.           );
  1483.  
  1484. Or to provide a status indicator:
  1485.  
  1486.     use MT::Entry;
  1487.     my $total = MT::Entry->count({ blog_id => $blog_id });
  1488.     my $i = 0;
  1489.     local $| = 1;
  1490.     $mt->rebuild(
  1491.               BlogID => $blog_id,
  1492.               EntryCallback => sub { printf "%d/%d\r", ++$i, $total },
  1493.           );
  1494.     print "\n";
  1495.  
  1496. This argument is optional; by default no callbacks are executed.
  1497.  
  1498. =item * NoIndexes
  1499.  
  1500. By default I<rebuild> will rebuild the index templates after rebuilding all
  1501. of the entries; if you do not want to rebuild the index templates, set the
  1502. value for this argument to a true value.
  1503.  
  1504. This argument is optional.
  1505.  
  1506. =item * Limit
  1507.  
  1508. Limit the number of entries to be rebuilt to the last C<N> entries in the
  1509. blog. For example, if you set this to C<20> and do not provide an offset (see
  1510. L<Offset>, below), the 20 most recent entries in the blog will be rebuilt.
  1511.  
  1512. This is only useful if you are rebuilding C<Individual> archives.
  1513.  
  1514. This argument is optional; by default all entries will be rebuilt.
  1515.  
  1516. =item * Offset
  1517.  
  1518. When used with C<Limit>, specifies the entry at which to start rebuilding
  1519. your individual entry archives. For example, if you set this to C<10>, and
  1520. set a C<Limit> of C<5> (see L<Limit>, above), entries 10-14 (inclusive) will
  1521. be rebuilt. The offset starts at C<0>, and the ordering is reverse
  1522. chronological.
  1523.  
  1524. This is only useful if you are rebuilding C<Individual> archives, and if you
  1525. are using C<Limit>.
  1526.  
  1527. This argument is optional; by default all entries will be rebuilt, starting
  1528. at the first entry.
  1529.  
  1530. =back
  1531.  
  1532. =head2 $mt->rebuild_entry( %args )
  1533.  
  1534. Rebuilds a particular entry in your blog (and its dependencies, if specified).
  1535.  
  1536. I<%args> can contain:
  1537.  
  1538. =over 4
  1539.  
  1540. =item * Entry
  1541.  
  1542. An I<MT::Entry> object corresponding to the object you would like to rebuild.
  1543.  
  1544. This argument is required.
  1545.  
  1546. =item * Blog
  1547.  
  1548. An I<MT::Blog> object corresponding to the blog to which the I<Entry> belongs.
  1549.  
  1550. This argument is optional; if not provided, the I<MT::Blog> object will be
  1551. loaded in I<rebuild_entry> from the I<$entry-E<gt>blog_id> column of the
  1552. I<MT::Entry> object passed in. If you already have the I<MT::Blog> object
  1553. loaded, however, it makes sense to pass it in yourself, as it will skip one
  1554. small step in I<rebuild_entry> (loading the object).
  1555.  
  1556. =item * BuildDependencies
  1557.  
  1558. Saving an entry can have effects on other entries; so after saving, it is
  1559. often necessary to rebuild other entries, to reflect the changes onto all
  1560. of the affected archive pages, indexes, etc.
  1561.  
  1562. If you supply this parameter with a true value, I<rebuild_indexes> will
  1563. rebuild: the archives for the next and previous entries, chronologically;
  1564. all of the index templates; the archives for the next and previous daily,
  1565. weekly, and monthly archives.
  1566.  
  1567. =item * Previous, Next, OldPrevious, OldNext
  1568.  
  1569. These values identify entries which may need to be updated now that
  1570. "this" entry has changed. When the created_on field of an entry is
  1571. changed, its new neighbors (Previous and Next) need to be rebuilt as
  1572. well as its former neighbors (OldPrevious and OldNext).
  1573.  
  1574. =item * NoStatic
  1575.  
  1576. When this value is true, it acts as a hint to the rebuilding routine
  1577. that static output files need not be rebuilt; the "rebuild" operation
  1578. is just to update the bookkeeping that supports dynamic rebuilds.
  1579.  
  1580. =back
  1581.  
  1582. =head2 $mt->rebuild_file($blog, $archive_root, $map, $archive_type, $ctx, \%cond, $build_static, %specifier)
  1583.  
  1584. Method responsible for building a single archive page from a template and
  1585. writing it to the file management layer.
  1586.  
  1587. I<$blog> refers to the target weblog. I<$archive_root> is the target archive
  1588. path to publish the file. I<$map> is a L<MT::TemplateMap> object that
  1589. relates to publishing the file. I<$archive_type> is one of "Daily",
  1590. "Weekly", "Monthly", "Category" or "Individual". I<$ctx> is a handle to
  1591. the L<MT::Template::Context> object to use to build the file. I<\%cond>
  1592. is a hashref to conditional arguments used to drive the build process.
  1593. I<$build_static> is a boolean flag that controls whether static files are
  1594. created (otherwise, the records necessary for serving dynamic pages are
  1595. created and that is all).
  1596.  
  1597. I<%specifier> is a hash that uniquely identifies the specific instance
  1598. of the given archive type. That is, for a category archive page it
  1599. identifies the category; for a date-based archive page it identifies
  1600. which time period is covered by the page; for an individual archive it
  1601. identifies the entry. I<%specifier> should contain just one of these
  1602. keys:
  1603.  
  1604. =over 4
  1605.  
  1606. =item * Category
  1607.  
  1608. A category ID or L<MT::Category> instance of the category archive page to
  1609. be built.
  1610.  
  1611. =item * Entry
  1612.  
  1613. An entry ID or L<MT::Entry> instance of the entry archive page to be
  1614. built.
  1615.  
  1616. =item * StartDate
  1617.  
  1618. The starting timestamp of the date-based archive to be built.
  1619.  
  1620. =back
  1621.  
  1622. =head2 $mt->rebuild_indexes( %args )
  1623.  
  1624. Rebuilds all of the index templates in your blog, or just one, if you use
  1625. the I<Template> argument (below). Only rebuilds templates that are set to
  1626. be rebuilt automatically, unless you use the I<Force> (below).
  1627.  
  1628. I<%args> can contain:
  1629.  
  1630. =over 4
  1631.  
  1632. =item * Blog
  1633.  
  1634. An I<MT::Blog> object corresponding to the blog whose indexes you would like
  1635. to rebuild.
  1636.  
  1637. Either this or C<BlogID> is required.
  1638.  
  1639. =item * BlogID
  1640.  
  1641. The ID of the blog whose indexes you would like to rebuild.
  1642.  
  1643. Either this or C<Blog> is required.
  1644.  
  1645. =item * Template
  1646.  
  1647. An I<MT::Template> object specifying the index template to rebuild; if you use
  1648. this argument, I<only> this index template will be rebuilt.
  1649.  
  1650. Note that if the template that you specify here is set to not rebuild
  1651. automatically, you I<must> specify the I<Force> argument in order to force it
  1652. to be rebuilt.
  1653.  
  1654. =item * Force
  1655.  
  1656. A boolean flag specifying whether or not to rebuild index templates who have
  1657. been marked not to be rebuilt automatically.
  1658.  
  1659. The default is C<0> (do not rebuild such templates).
  1660.  
  1661. =back
  1662.  
  1663. =head2 $mt->ping( %args )
  1664.  
  1665. Sends all configured XML-RPC pings as a way of notifying other community
  1666. sites that your blog has been updated.
  1667.  
  1668. I<%args> can contain:
  1669.  
  1670. =over 4
  1671.  
  1672. =item * Blog
  1673.  
  1674. An I<MT::Blog> object corresponding to the blog for which you would like to
  1675. send the pings.
  1676.  
  1677. Either this or C<BlogID> is required.
  1678.  
  1679. =item * BlogID
  1680.  
  1681. The ID of the blog for which you would like to send the pings.
  1682.  
  1683. Either this or C<Blog> is required.
  1684.  
  1685. =back
  1686.  
  1687. =head2 $mt->ping_and_save( %args )
  1688.  
  1689. Handles the task of issuing any pending ping operations for a given
  1690. entry and then saving that entry back to the database.
  1691.  
  1692. The I<%args> hash should contain an element named C<Entry> that is a
  1693. reference to a L<MT::Entry> object.
  1694.  
  1695. =head2 $mt->set_language($tag)
  1696.  
  1697. Loads the localization plugin for the language specified by I<$tag>, which
  1698. should be a valid and supported language tag--see I<supported_languages> to
  1699. obtain a list of supported languages.
  1700.  
  1701. The language is set on a global level, and affects error messages and all
  1702. text in the administration system.
  1703.  
  1704. This method can be called as either a class method or an object method; in
  1705. other words,
  1706.  
  1707.     MT->set_language($tag)
  1708.  
  1709. will also work. However, the setting will still be global--it will not be
  1710. specified to the I<$mt> object.
  1711.  
  1712. The default setting--set when I<MT::new> is called--is U.S. English. If a
  1713. I<DefaultLanguage> is set in F<mt.cfg>, the default is then set to that
  1714. language.
  1715.  
  1716. =head2 MT->translate($str[, $param, ...])
  1717.  
  1718. Translates I<$str> into the currently-set language (set by I<set_language>),
  1719. and returns the translated string. Any parameters following I<$str> are
  1720. passed through to the C<maketext> method of the active localization module.
  1721.  
  1722. =head2 MT->translate_templatized($str)
  1723.  
  1724. Translates a string that has embedded E<lt>MT_TRANSE<gt> tags. These
  1725. tags identify the portions of the string that require localization.
  1726. Each tag is processed separately and passed through the MT->translate
  1727. method. Examples (used in your application's HTML::Template templates):
  1728.  
  1729.     <p><MT_TRANS phrase="Hello, world"></p>
  1730.  
  1731. and
  1732.  
  1733.     <p><MT_TRANS phrase="Hello, [_1]" params="<TMPL_VAR NAME=NAME>"></p>
  1734.  
  1735. =head2 $mt->trans_error( $str[, $arg1, $arg2] )
  1736.  
  1737. Translates I<$str> into the currently-set language (set by I<set_language>),
  1738. and assigns it as the active error for the MT instance. It returns undef,
  1739. which is the usual return value upon generating an error in the application.
  1740. So when an error occurs, the typical return result would be:
  1741.  
  1742.     if ($@) {
  1743.         return $app->trans_error("An error occurred: [_1]", $@);
  1744.     }
  1745.  
  1746. The optional I<$arg1> (and so forth) parameters are passed as parameters to
  1747. any parameterized error message.
  1748.  
  1749. =head2 $mt->current_language
  1750.  
  1751. Returns the language tag for the currently-set language.
  1752.  
  1753. =head2 MT->supported_languages
  1754.  
  1755. Returns a reference to an associative array mapping language tags to their
  1756. proper names. For example:
  1757.  
  1758.     use MT;
  1759.     my $langs = MT->supported_languages;
  1760.     print map { $_ . " => " . $langs->{$_} . "\n" } keys %$langs;
  1761.  
  1762. =head2 MT->add_plugin($plugin)
  1763.  
  1764. Adds the plugin described by $plugin to the list of plugins displayed
  1765. on the welcome page. The argument should be an object of the
  1766. I<MT::Plugin> class.
  1767.  
  1768. =head2 MT->add_plugin_action($where, $action_link, $link_text)
  1769.  
  1770. Adds a link to the given plugin action from the location specified by
  1771. $where. This allows plugins to create actions that apply to, for
  1772. example, the entry which the user is editing. The type of object the
  1773. user was editing, and its ID, are passed as parameters.
  1774.  
  1775. Values that are used from  the $where parameter are as follows:
  1776.  
  1777. =over 4
  1778.  
  1779. =item * list_entries
  1780.  
  1781. =item * list_commenters
  1782.  
  1783. =item * list_comments
  1784.  
  1785. =item * <type>
  1786. (Where <type> is any object that the user can already edit, such as
  1787. 'entry,' 'comment,' 'commenter,' 'blog,' etc.)
  1788.  
  1789. =back
  1790.  
  1791. The C<$where> value will be passed to the given action_link as a CGI
  1792. parameter called C<from>. For example, on the list_entries page, a
  1793. link will appear to:
  1794.  
  1795.     <action_link>&from=list_entries
  1796.  
  1797. If the $where is a single-item page, such as an entry-editing page,
  1798. then the action_link will also receive a CGI parameter C<id> whose
  1799. value is the ID of the object under consideration:
  1800.  
  1801.     <action_link>&from=entry&id=<entry-id>
  1802.  
  1803. Note that the link is always formed by appending an ampersand. Thus,
  1804. if your $action_link is simply the name of a CGI script, such as
  1805. my-plugin.cgi, you'll want to append a '?' to the argument you pass:
  1806.  
  1807.     MT->add_plugin_action('entry', 'my-plugin.cgi?', \
  1808.                           'Touch this entry with MyPlugin')
  1809.  
  1810. Finally, the $link_text parameter specifies the text of the link; this
  1811. value will be wrapped in E<lt>a> tags that point to the $action_link.
  1812.  
  1813. =head2 MT->add_text_filter($key, \%options)
  1814.  
  1815. Adds a text filter with the short name I<$key> and the options in
  1816. I<\%options>.
  1817.  
  1818. The text filter will be added to MT's list of text filtering options in
  1819. the new/edit entry screen, and will be used for filtering all of the entry
  1820. fields, if the user has enabled filtering for those fields in the template
  1821. (for example, by default the entry body and extended text are both run
  1822. through the filter, but the excerpt is not).
  1823.  
  1824. I<$key> should be a lower-case identifier containing only
  1825. alphanumerics and C<_> (that is, matching C</\w+/>). Since I<$key> is
  1826. stored as the filter name on a per-entry basis, it B<should not change>.
  1827. (In other words, don't call if I<foo> in one version and I<foo_bar> in
  1828. the next, if the filter does the same thing in each version.)
  1829.  
  1830. The flip side of this, though, is that if your filter acts differently
  1831. from one version to the next, you B<should> change I<$key>, and you
  1832. should also change the filename of your plugin, so that the old
  1833. implementation--which may be associated with all of the entries in the user's
  1834. system--still works as usual. For example, if your C<foo> plugin changes
  1835. semantics drastically so that paragraph breaks are represented as two
  1836. C<E<lt>br /E<gt>> tags, rather than C<E<lt>pE<gt>> tags, you should change
  1837. the key of the new version to C<foo_2> (for example), and the filename to
  1838. F<foo_2.pl>.
  1839.  
  1840. I<%options> can contain:
  1841.  
  1842. =over 4
  1843.  
  1844. =item * label
  1845.  
  1846. The short-but-descriptive label for the filter. This will be displayed in
  1847. the Movable Type UI as the name of the text filter.
  1848.  
  1849. =item * on_format
  1850.  
  1851. A reference to a subroutine that will be executed to filter a string of
  1852. text. The subroutine will always receive one argument, the string of text to
  1853. filter, and should return the filtered string. In some cases--for example,
  1854. when called while building a template--the subroutine will receive a
  1855. second argument, the I<MT::Template::Context> object handling the build.
  1856.  
  1857. See the example below.
  1858.  
  1859. =item * docs
  1860.  
  1861. The URL (or filename) of a document containing documentation on your filter.
  1862. This will be displayed in a popup window when the user selects your filter
  1863. on the New/Edit Entry screen, then clicks the Help link (C<(?)>).
  1864.  
  1865. If the value is a full URL (starting with C<http://>), the popup window
  1866. will open with that URL; otherwise, it is treated as a filename, assumed to
  1867. be in the user's F<docs> folder.
  1868.  
  1869. =back
  1870.  
  1871. Here's an example of adding a text filter for Wiki formatting, using the
  1872. I<Text::WikiFormat> CPAN module:
  1873.  
  1874.     MT->add_text_filter(wiki => {
  1875.         label => 'Wiki',
  1876.         on_format => sub {
  1877.             require Text::WikiFormat;
  1878.             Text::WikiFormat::format($_[0]);
  1879.         },
  1880.         docs => 'http://www.foo.com/mt/wiki.html',
  1881.     });
  1882.  
  1883. =head2 MT->all_text_filters
  1884.  
  1885. Returns a reference to a hash containing the registry of text filters.
  1886.  
  1887. =head2 MT->apply_text_filters($str, \@filters)
  1888.  
  1889. Applies the set of filters I<\@filters> to the string I<$str> and returns
  1890. the result (the filtered string).
  1891.  
  1892. I<\@filters> should be a reference to an array of filter keynames--these
  1893. are the short names passed in as the first argument to I<add_text_filter>.
  1894. I<$str> should be a scalar string to be filtered.
  1895.  
  1896. If one of the filters listed in I<\@filters> is not found in the list of
  1897. registered filters (that is, filters added through I<add_text_filter>),
  1898. it will be skipped silently. Filters are executed in the order in which they
  1899. appear in I<\@filters>.
  1900.  
  1901. As it turns out, the I<MT::Entry::text_filters> method returns a reference
  1902. to the list of text filters to be used for that entry. So, for example, to
  1903. use this method to apply filters to the main entry text for an entry
  1904. I<$entry>, you would use
  1905.  
  1906.     my $out = MT->apply_text_filters($entry->text, $entry->text_filters);
  1907.  
  1908. =head2 MT->add_callback($meth, $priority, $plugin, $code)
  1909.  
  1910. Registers a new callback handler for a particular registered callback.
  1911.  
  1912. The first parameter is the name of the callback method. The second
  1913. parameter is a priority (a number in the range of 1-10) which will control
  1914. the order that the handler is executed in relation to other handlers. If
  1915. two handlers register with the same priority, they will be executed in
  1916. the order that they registered. The third parameter is a C<MT::Plugin> object
  1917. reference that is associated with the handler (this parameter is optional).
  1918. The fourth parameter is a code reference that is invoked to handle the
  1919. callback. For example:
  1920.  
  1921.     MT->add_callback('BuildFile', 1, undef, \&rebuild_file_hdlr);
  1922.  
  1923. The code reference should expect to receive an object of type
  1924. MT::Errorhandler as its first argument. This object is used to
  1925. communicate errors to the caller:
  1926.  
  1927.     sub rebuild_file_hdlr {
  1928.         my ($eh, ...) = @_;
  1929.         if (something bad happens) {
  1930.             return $eh->error("Something bad happened!");
  1931.         }
  1932.     }
  1933.  
  1934. Other parameters to the callback function depend on the callback point.
  1935.  
  1936. The treatment of the error string depends on the callback point.
  1937. Typically, either it is ignored or the user's action fails and the
  1938. error message is displayed.
  1939.  
  1940. =head2 MT->run_callbacks($meth[, $arg1, $arg2, ...])
  1941.  
  1942. Invokes a particular callback, running any associated callback handlers.
  1943.  
  1944. The first parameter is the name of the callback to execute. This is one
  1945. of the global callback methods (see L<Callbacks> section) or can be
  1946. a class-specific method that includes the package name associated with
  1947. the callback.
  1948.  
  1949. The remaining arguments are passed through to any callback handlers that
  1950. are invoked.
  1951.  
  1952. For "Filter"-type callbacks, this routine will return a 0 if any of the
  1953. handlers return a false result. If all handlers return a true result,
  1954. a value of 1 is returned.
  1955.  
  1956. Example:
  1957.  
  1958.     MT->run_callbacks('MyClass::frobnitzes', \@whirlygigs);
  1959.  
  1960. Which would execute any handlers that registered in this fashion:
  1961.  
  1962.     MT->add_callback('MyClass::frobnitzes', 4, $plugin, \&frobnitz_hdlr);
  1963.  
  1964. =head2 MT->VERSION
  1965.  
  1966. Returns the version of MT (including any beta/alpha designations).
  1967.  
  1968. =head2 MT->version_number
  1969.  
  1970. Returns the numeric version of MT (without any beta/alpha designations).
  1971. For example, if I<VERSION> returned C<2.5b1>, I<version_number> would
  1972. return C<2.5>.
  1973.  
  1974. =head1 ERROR HANDLING
  1975.  
  1976. On an error, all of the above methods return C<undef>, and the error message
  1977. can be obtained by calling the method I<errstr> on the class or the object
  1978. (depending on whether the method called was a class method or an instance
  1979. method).
  1980.  
  1981. For example, called on a class name:
  1982.  
  1983.     my $mt = MT->new or die MT->errstr;
  1984.  
  1985. Or, called on an object:
  1986.  
  1987.     $mt->rebuild(BlogID => $blog_id)
  1988.         or die $mt->errstr;
  1989.  
  1990. =head1 CALLBACKS
  1991.  
  1992. Movable Type has a variety of hook points at which a plugin can attach
  1993. a callback. The context and calling conventions of each one are
  1994. documented here.
  1995.  
  1996. In each case, the first parameter is an MT::ErrorHandler object which
  1997. can be used to pass error information back to the caller.
  1998.  
  1999. The app-level callbacks related to rebuilding are documented
  2000. below. The specific apps document the callbacks which they invoke.
  2001.  
  2002. =over 4
  2003.  
  2004. =item BuildFileFilter
  2005.  
  2006. This filter gives plugins the chance to determine whether a given file
  2007. should be rebuilt as part of a given rebuild event.
  2008.  
  2009. A BuildFileFilter callback has the following signature:
  2010.  
  2011.     sub build_file_filter($eh, %args)
  2012.     {
  2013.         ...
  2014.         return <boolean>;
  2015.     }
  2016.  
  2017. As with other callback funcions, the first parameter is an
  2018. C<MT::ErrorHandler> object. This can be used by the callback to
  2019. propagate an error message to the surrounding context.
  2020.  
  2021. The C<%args> parameters identify the page to be built. See
  2022. L<MT::FileInfo> for more information on how a page is determined by
  2023. these parameters. Elements in C<%args> are as follows:
  2024.  
  2025. =over 4
  2026.  
  2027. =item C<Context>
  2028.  
  2029. Holds the template context that has been constructed for building (see
  2030. C<MT::Template::Context>).
  2031.  
  2032. =item C<ArchiveType> 
  2033.  
  2034. The archive type of the file, usually one of C<'Index'>,
  2035. C<'Individual'>, C<'Category'>, C<'Daily'>, C<'Monthly'>, or
  2036. C<'Weekly'>.
  2037.  
  2038. =item C<Templatemap>
  2039.  
  2040. An C<MT::TemplateMap> object; this singles out which template is being
  2041. built, and the filesystem path of the file to be written.
  2042.  
  2043. =item C<Blog>
  2044.  
  2045. The C<MT::Blog> object representing the blog whose pages are being
  2046. rebuilt.
  2047.  
  2048. =item C<Entry>
  2049.  
  2050. In the case of an individual archive page, this points to the
  2051. C<MT::Entry> object whose page is being rebuilt. In the case of an
  2052. archive page other than an individual page, this parameter is not
  2053. necessarily undefined. It is best to rely on the C<$at> parameter to
  2054. determine whether a single entry is on deck to be built.
  2055.  
  2056. =item C<PeriodStart> 
  2057.  
  2058. In the case of a date-based archive page, this is a timestamp at the
  2059. beginning of the period from which entries will be included on this
  2060. page, in Movable Type's standard 14-digit "timestamp" format. For
  2061. example, if the page is a Daily archive for April 17, 1796, this value
  2062. would be 17960417000000. If the page were a Monthly archive for March,
  2063. 2003, C<$start> would be 20030301000000. Again, this parameter may be
  2064. defined even when the page on deck is not a date-based archive page.
  2065.  
  2066. =item C<Category>
  2067.  
  2068. In the case of a Category archive, this parameter identifies the
  2069. category which will be built on the page.
  2070.  
  2071. =back
  2072.  
  2073. =item BuildFile
  2074.  
  2075. BuildFile callbacks are invoked just after a file has been built.
  2076.  
  2077.     sub build_file($eh, %args)
  2078.     {
  2079.     }
  2080.  
  2081. Parameters in %args are as with BuildFileFilter. There is one
  2082. additional key-value pair, C<FileInfo>, an C<MT::FileInfo> object which
  2083. contains information about the file. See L<MT::FileInfo> for more
  2084. information about what a C<MT::FileInfo> contains. Chief amongst all
  2085. the members of C<MT::FileInfo>, for these purposes, will be the
  2086. C<virtual> member. This is a boolean value which will be false if a
  2087. page was actually created on disk for this "page," and false if no
  2088. page was created (because the corresponding template is set to be
  2089. built dynamically).
  2090.  
  2091. =back
  2092.  
  2093. =head1 LICENSE
  2094.  
  2095. The license that applies is the one you agreed to when downloading
  2096. Movable Type.
  2097.  
  2098. =head1 AUTHOR & COPYRIGHT
  2099.  
  2100. Except where otherwise noted, MT is Copyright 2001-2005 Six Apart.
  2101. All rights reserved.
  2102.  
  2103. =cut
  2104.